package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.gulimall.common.utils.PageUtils;
import com.atguigu.gulimall.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {

        List<CategoryEntity> entities = baseMapper.selectList(null);

        // 找到一级菜单
        List<CategoryEntity> collect = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu) -> {
            menu.setChild(getChildren(menu, entities));
            return menu;
        }).sorted((menu1, menu2) -> {
            // 菜单排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());


        return collect;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO 检查当前的菜单是否被别的地方所引用
//        categoryDao.deleteBatchIds(asList);
    }

    @Override // CategoryServiceImpl
    public Long[] findCatelogPathById(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        paths = findParentPath(catelogId, paths);
        // 收集的时候是顺序 前端是逆序显示的 所以用集合工具类给它逆序一下
        // 子父 转 父子
        Collections.reverse(paths);
        return paths.toArray(new Long[paths.size()]); // 1级  2级  3级
    }

    /**
     * Spring-Cache的不足
     * 1）、读模式
     *      缓存穿透：查询一个null数据，解决，缓存空数据 cache-null-values=true
     *      缓存击穿：大量并发同时查询一个正好过期的数据。解决：加锁：默认是无加锁的 sync=true(加本地锁)不是分布式锁，影响不大
     *      缓存血本：大量的key同时过期。解决：加随机时间。加上过期时间：spring.cache.redis.time-ti-live=3600000
     * 2）、写模式：(缓存与数据库一致)
     *      1、读写加锁
     *      2、引入Canal、感知到MySQL的更新去更新数据库
     *      3、读多写多、直接去数据库查询就行
     *3）、总结：
     *      常规数据（读多写少，即时性，一致性要求不高的数据，完全可以使用Spring-Cache）：
     *      写模式(只要缓存的数据有过期时间就足够了)
     *      特殊数据：特殊设计
     */

    /**
     * 级联更新所有关联数据
     * @CacheEvict:失效模式 ：删除后缓存删除，再次查时，才新增缓存
     * 1.@Caching 同时进行多种缓存操作
     * 2.@CacheEvict(value = "category",allEntries = true)指定删除某个分区下的所有数据
     *
     * @param category
     */
    @Transactional
//    @Caching(evict = {
//            @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
//            @CacheEvict(value = "category",key = "'getCatalogJson'")
//    })
    @CacheEvict(value = "category",allEntries = true)
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }



    @Cacheable(value = {"category"},key = "#root.methodName",sync = true)//代表当前方法的结果需要缓存，如果缓存中有，方法不用调用，如果缓存中没有，会调用方法，最后将方法的结果放入缓存
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }


    //从数据库中查出三级分类
    private Map<String, List<Catelog2Vo>> getCategoriesDb() {
        //优化业务逻辑，仅查询一次数据库
        List<CategoryEntity> categoryEntities = this.list();
        //查出所有一级分类
        List<CategoryEntity> level1Categories = getCategoryByParentCid(categoryEntities, 0L);
        Map<String, List<Catelog2Vo>> listMap = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //遍历查找出二级分类
            List<CategoryEntity> level2Categories = getCategoryByParentCid(categoryEntities, v.getCatId());
            List<Catelog2Vo> catalog2Vos = null;
            if (level2Categories != null) {
                //封装二级分类到vo并且查出其中的三级分类
                catalog2Vos = level2Categories.stream().map(cat -> {
                    //遍历查出三级分类并封装
                    List<CategoryEntity> level3Catagories = getCategoryByParentCid(categoryEntities, cat.getCatId());
                    List<Catelog2Vo.Catalog3Vo> catalog3Vos = null;
                    if (level3Catagories != null) {
                        catalog3Vos = level3Catagories.stream()
                                .map(level3 -> new Catelog2Vo.Catalog3Vo(level3.getParentCid().toString(), level3.getCatId().toString(), level3.getName()))
                                .collect(Collectors.toList());
                    }
                    Catelog2Vo catalog2Vo = new Catelog2Vo(v.getCatId().toString(), cat.getCatId().toString(), cat.getName(), catalog3Vos);
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));
        return listMap;
    }

    private List<CategoryEntity> getCategoryByParentCid(List<CategoryEntity> categoryEntities, long l) {
        List<CategoryEntity> collect = categoryEntities.stream().filter(cat -> cat.getParentCid() == l).collect(Collectors.toList());
        return collect;
    }

    /**
     * 递归收集所有父分类
     */
    private List<Long> findParentPath(Long catlogId, List<Long> paths) {
        // 1、收集当前节点id
        paths.add(catlogId);// 比如父子孙层级，返回的是 孙 子 父
        CategoryEntity byId = this.getById(catlogId);
        if (byId.getParentCid() != 0) {
            // 递归
            findParentPath(byId.getParentCid(), paths);
        }
        return paths;
    }

    // 找到一级菜单下的子菜单,递归查找菜单的所有子菜单
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> collect = all.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == root.getCatId()
        )
                .map((menu) -> {
                    // 找到子菜单
                    menu.setChild(getChildren(menu, all));
                    return menu;
                })
                .sorted((menu1, menu2) -> {
                    // 菜单排序
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());

        return collect;
    }



    @Override
    @Cacheable(value = "category",key = "#root.methodName")
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        Map<String, List<Catelog2Vo>> categoriesDb = getCategoriesDb();
        return categoriesDb;
    }
//
//    public Map<String, List<Catelog2Vo>> getCatalogJsonDbWithRedisLock() {
//        String uuid = UUID.randomUUID().toString();
//        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//        Boolean lock = ops.setIfAbsent("lock", uuid, 500, TimeUnit.SECONDS);
//        if (lock) {
//            Map<String, List<Catelog2Vo>> categoriesDb = getCategoryMap();
//            String lockValue = ops.get("lock");
//            // get和delete原子操作
//            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
//                    "    return redis.call(\"del\",KEYS[1])\n" +
//                    "else\n" +
//                    "    return 0\n" +
//                    "end";
//            stringRedisTemplate.execute(
//                    new DefaultRedisScript<Long>(script, Long.class), // 脚本和返回类型
//                    Arrays.asList("lock"), // 参数
//                    lockValue); // 参数值，锁的值
//            return categoriesDb;
//        } else {
//            try {
//                Thread.sleep(100);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            // 睡眠0.1s后，重新调用 //自旋
//            return getCatalogJsonDbWithRedisLock();
//        }
//    }
//    private Map<String, List<Catelog2Vo>> getCategoryMap() {
//        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//        String catalogJson = ops.get("catalogJson");
//        if (StringUtils.isEmpty(catalogJson)) {
//            System.out.println("缓存不命中，准备查询数据库。。。");
//            Map<String, List<Catelog2Vo>> categoriesDb = getCategoriesDb();
//            String toJSONString = JSON.toJSONString(categoriesDb);
//            ops.set("catalogJson", toJSONString);
//            return categoriesDb;
//        }
//        System.out.println("缓存命中。。。。");
//        Map<String, List<Catelog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
//        });
//        return listMap;
//    }
}